# End-to-End Examples

import { Callout } from 'nextra/components';

<Callout type="warning">
	Sui Owned Object Pools (SuiOOP) will likely be replaced by
	[`ParallelTransactionExecutor`](../executors#paralleltransactionexecutor) from
	`@mysten/sui/transactions`

    Sui Owned Object Pools (SuiOOP) is a beta library. Enhancements and changes are likely during
    development.

</Callout>

These examples demonstrate some possible use cases for the Sui Owned Object Pool library.

## Use case 1: Parallel coin transfers service—Multiple Coins

Assume that you have a service that needs to make payments of size 50000000 MIST to multiple
recipients in parallel.

Before creating an `ExecutorServiceHandler` instance that executes each incoming transaction, you
first need to have a set of coins to use for the coin transferring and to pay for the gas of each
transaction.

<Callout type="info">
	`ExecutorServiceHandler` creates worker pools that handle the execution of the transactions. The
	maximum number of worker pools the handler can create is tightly coupled with the number of your
	account's coins.
</Callout>

Here is the code that creates the coins by splitting a single coin 20 times and transferring the new
coins to your address:

```ts
import { SuiClient } from '@mysten/sui/client/';
import type { SuiObjectRef, SuiObjectResponse } from '@mysten/sui/client/';
import { Transaction } from '@mysten/sui/transactions';

const client = new SuiClient({
	url: 'https://fullnode.testnet.sui.io:443',
});

const objectId: string = '<your-coin-object-id>'; // A
const yourAddressSecretKey: string = '<your-address-secret-key>';

const numberOfCoinsToCreate = 5;
/// Splits a specific coin and then transfer the new coins to the same address.
for (let i = 0; i < numberOfCoinsToCreate; i++) {
	await splitCoinAndTransferToSelf(client, objectId, yourAddressSecretKey);
}

async function splitCoinAndTransferToSelf(
	client: SuiClient,
	coinObjectId: string,
	yourAddressSecretKey: string,
) {
	const tx = new Transaction();
	const coinToPay = await client.getObject({ id: coinObjectId });
	const newcoins1 = tx.splitCoins(tx.gas, [tx.pure(300000000)]);
	// const newcoins2 = tx.splitCoins(tx.gas, [tx.pure(300000000)]);
	tx.transferObjects(
		[
			newcoins1,
			// newcoins2
		],
		tx.pure(getKeyPair(yourAddressSecretKey).getPublicKey().toSuiAddress()),
	);
	tx.setGasPayment([toSuiObjectRef(coinToPay)]);
	tx.setGasBudget(100000000);
	await client
		.signAndExecuteTransaction({
			signer: getKeyPair(yourAddressSecretKey),
			transaction: tx,
			requestType: 'WaitForLocalExecution',
			options: {
				showEffects: true,
			},
		})
		.then((txRes) => {
			const status = txRes.effects?.status?.status;
			if (status !== 'success') {
				throw new Error(`Could not split coin! ${txRes.effects?.status?.error}`);
			}
			return txRes;
		})
		.catch((err) => {
			throw new Error(`Error thrown: Could not split coin!: ${err}`);
		});
}
```

Now that you have the coins, you can create the `ExecutorServiceHandler` instance and execute the
transactions.

The library provides the `DefaultSplitStrategy` as a parameter to the `ExecutorServiceHandler`
constructor setting the `minimumBalance` equal to 300000000, meaning that the worker pool that is
created needs to have a set of coins that the sum of their balances is greater than or equal to
this.

```ts
import { DefaultSplitStrategy, ExecutorServiceHandler } from 'suioop';

// Setup the executor service
const eshandler = await ExecutorServiceHandler.initialize(adminKeypair, client);
// Define the number of transactions to execute
const promises = [];
let tx: TransactionBlockWithLambda;
for (let i = 0; i < 10; i++) {
	tx = new TransactionBlockWithLambda(
		() => createPaymenttx('<recipient-address>'), // Use a test user address to receive the txs
	);
	promises.push(
		eshandler.execute(
			tx,
			client,
			new DefaultSplitStrategy(300000000), // Each pool contains coins with a total balance of 300000000 MIST
		),
	);
}

// Collect the promise results
const results = await Promise.allSettled(promises);
```

<Callout type="info">
	Providing a `SplitStrategy` is optional. If you don't provide one, the `DefaultSplitStrategy` is
	used, which also has a default minimum balance. For demonstration purposes, the minimum pool
	balance here is 300000000.
</Callout>

## Use case 2: mint and transfer NFTs concurrently using multiple AdminCaps

Assume that you have a service that needs to mint and transfer NFTs concurrently to multiple
recipients. Similar to the previous example, you first need to have a set of coins that are used to
pay for the gas of each transaction. In addition, you need to have a set of `AdminCaps` to mint the
NFTs.

In addition to the coin creation performed earlier, you need to create multiple `AdminCaps` for your
account.

To do this, you can either use the `createAdminCap` function in your smart contract, or generate
multiple admin caps on package publishing. You can test this by using the
[`move_examples/nft_app` example](https://github.com/MystenLabs/Sui_Owned_Object_Pools/tree/main/move_examples/nft_app).
Notice how multiple `AdminCaps` are created in
[`genesis.move`](https://github.com/MystenLabs/Sui_Owned_Object_Pools/blob/main/move_examples/nft_app/sources/genesis.move):

```ts
// ...
// Generate 20 AdminCaps, for parallelization of transactions
let i = 0;
while (i <= 20) {
  // Transfer AdminCap to sender
  transfer::public_transfer(AdminCap {
  id: object::new(ctx)
  }, sender(ctx));
  i = i + 1;
}
// ...
```

Now that you have the coins and the admin caps, you can define the `mintNFTtx` function that creates
and returns the transaction of type `TransactionBlockWithLambda`.

```ts
function mintNFTtx(
	env: EnvironmentVariables,
	adminKeypair: Ed25519Keypair,
): TransactionBlockWithLambda {
	const txLambda = (adminCapId: string) => {
		const tx = new Transaction();
		const hero = tx.moveCall({
			arguments: [
				tx.object(adminCapId),
				tx.pure('zed'),
				tx.pure('gold'),
				tx.pure(3),
				tx.pure('ipfs://example.com/'),
			],
			target: `${env.NFT_APP_PACKAGE_ID}::hero_nft::mint_hero`,
		});

		tx.transferObjects([hero], tx.pure(adminKeypair.getPublicKey().toSuiAddress()));
		tx.setGasBudget(10000000);
		return tx;
	};
	return new TransactionBlockWithLambda(txLambda, ['AdminCap']);
}
```

Having done the preparatory steps above, it's time to create the `ExecutorServiceHandler` instance
and execute the transactions.

```ts
const eshandler = await ExecutorServiceHandler.initialize(adminKeypair, client);
const promises: Promise<SuiTransactionBlockResponse>[] = [];
let tx: TransactionBlockWithLambda;
for (let i = 0; i < NUMBER_OF_TRANSACTION_TO_EXECUTE; i++) {
	tx = mintNFTtx(env, adminKeypair);
	promises.push(
		eshandler.execute(tx, client, new IncludeAdminCapStrategy('<your-nft-package-id>')),
	);
}

const results = await Promise.allSettled(promises);
results.forEach((result) => {
	if (result.status === 'rejected') {
		console.error(result.reason);
	}
});
```
